﻿using System.Collections.Generic;
using System;
using System.Text;
using UnityEngine;

namespace GameBasic.BehaviorTree
{
    public class Bhv
    {
        public enum Type
        {
            None = 0,
            Root = 1,
            Selector = 10,
            Priority = 20,
            // wait previous end
            PrioWait = 21,
            Sequence = 30,
            Parallel = 40,
            Action = 50
        };

        #region Attributes
        public string name;

        public Type type = Type.Action;

        // condition to active
        public Func<bool> startCondition;
        // end condtion
        public Func<bool> stopCondition;

        // Timers
        // duration
        public BehaviorTimer duration;
        // cd
        public CDTimer cdTimer;

        //
        // callbacks
        public bool stopOnError = false;
        public Action[] timeTickAct;
        public Action[] startAct;
        public Action[] updateAct;
        public Action[] stopAct;

        // state
        bool active;

        // Tree
        // runtime remove flag
        bool removeFlag = false;

        public int nodeLevel;
        public int nodeIndex;
        protected Bhv _parent;
        public List<Bhv> children;
        uint activeChild;

        public Bhv Parent
        {
            get { return _parent; }
            set
            {
                if (_parent != value)
                {
                    // remove from old
                    if (_parent != null)
                        _parent.Remove(this);
                    // add as child to new
                    if (value != null)
                        value.Add(this);

                    _parent = value;
                }
            }
        }
        /// <summary>
        /// Children count
        /// </summary>
        public int Count { get { return children == null ? 0 : children.Count; } }

        #endregion

        // Callback Interfaces
        #region Callback Interfaces
        public void OnStart(params Action[] action)
        {
            AddAction(action, ref startAct);
        }

        public void OnUpdate(params Action[] action)
        {
            AddAction(action, ref updateAct);
        }

        public void OnStop(params Action[] action)
        {
            AddAction(action, ref stopAct);
        }

        public void OnTimeTick(params Action[] action)
        {
            AddAction(action, ref timeTickAct);
        }

        void AddAction(Action action, ref Action[] des)
        {
            if (des == null)
                des = new Action []{ action };
            else
                des = ArrayTools.PushLast(des, action);
        }

        void AddAction(Action[] action, ref Action[] des)
        {
            if (des == null)
                des = action;
            else
                des = ArrayTools.Concat(action, des);
        }

        /// <summary>
        /// Set <see cref="startCondition"/> to <see cref="BasicValue.True"/>
        /// </summary>
        public void StartTrue()
        {
            startCondition = BasicValue.True;
        }

        /// <summary>
        /// Set <see cref="stopCondition"/> to <see cref="BasicValue.True"/>
        /// </summary>
        public void StopTrue()
        {
            stopCondition = BasicValue.True;
        }


        #endregion

        // Condition, TimeTick, Start, Update, Stop
        #region Primary methods

        public Bhv()
        {}

        public Bhv(string name)
        {
            this.name = name;
        }

        /// <summary>
        /// Is running? Been called <see cref="Start"/> and not yes <see cref="Stop"/>
        /// </summary>
        /// <returns></returns>
        public bool IsActive()
        {
            return active;
        }

        /// <summary>
        /// Is on start condition
        /// Return <see cref="startCondition"/> value, start condition is not set, results differ based on type:
        /// <see cref="Type.Selector"/> <see cref="Type.Priority"/> <see cref="Type.Parallel"/> return true if any child is on start.
        /// <see cref="Type.Sequence"/> return true if first child is on start.
        /// <see cref="Type.Action"/> return true.
        /// <see cref="Type.Root"/> return false;
        /// </summary>
        /// <returns>
        /// </returns>
        public bool IsOnStart()
        {
            
            // remove mark
            if (removeFlag)
                return false;

            // CD
            if (cdTimer.Enabled && !cdTimer.ReachTarget())
                return false;

            // no start condition
            if (startCondition == null)
                switch (type)
                {
                    case Type.Selector:
                    case Type.Priority:
                    case Type.PrioWait:
                    case Type.Parallel:
                        // is any child on condition
                        int count = Count;
                        for (int i = 0; i < count; i++)
                            if (children[i].IsOnStart())
                                return true;
                        return false;
                    case Type.Sequence:
                        // is the first child on condition
                        return Count > 0 && children[0].IsOnStart();
                    case Type.Action:
                        return true;
                    default:
                        return false;
                }

            return startCondition();
        }

        /// <summary>
        /// Is on stop condition
        /// </summary>
        /// <returns></returns>
        public bool IsOnStop()
        {
            // Duration end
            if (duration.Enabled && duration.ReachTarget())
                return true;

            return stopCondition != null && stopCondition();
        }

        void TimeTick()
        {
            UpdateTimers();
            // invoke onTimeTick callback
            RunAction(timeTickAct);

            // invoke children
            // children?.ForEach(c => c.TimeTick());
            int count = Count;
            for (int i = 0; i < count; i++)
                children[i].TimeTick();
        }

        private void UpdateTimers()
        {
            // duration timer
            if (duration.Enabled)
                duration.Update();
            // cd timer
            if (cdTimer.Enabled)
                cdTimer.Update();
        }

        public void Start()
        {
            if (active)
                return;
            active = true;

            StartTimer();
            // invoke onStart Callback
            RunAction(startAct);
        }

        void StartTimer()
        {
            // reset duration timer
            if (duration.Enabled)
            {
                // start timer
                duration.Start();
            }

            // start cd timer
            if (cdTimer.Enabled && cdTimer.startInit)
                cdTimer.Start();
        }

        public void Update()
        {
            // invoke onUpdate callback
            RunAction(updateAct);

            // Composer
            if(IsComposer())
                ComposerUpdate();
            // Action, after execution, check stop
            else if (IsOnStop())
                Stop();
        }

        public void Stop()
        {
            if (!active)
                return;
            active = false;

            // start cd timer on stop?
            if (cdTimer.Enabled && !cdTimer.startInit)
                cdTimer.Start();

            // invoke onStop callback
            RunAction(stopAct);

            // Type: Composer
            if (IsComposer())
                ComposerStop();

            // stop all children
            for (int i = 0, length = Count; i < length; i++)
                children[i].Stop();
        }

        /// <summary>
        /// Execute action, stop when error occur
        /// </summary>
        /// <param name="action"></param>
        void RunAction(Action action)
        {
            if (action == null)
                return;

            if (stopOnError)
            {
                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    Stop();
                    throw (ex);
                }
            }
            else
            {
                action();
            }
        }

        /// <summary>
        /// Execute actions, stop when error occur
        /// </summary>
        /// <param name="action"></param>
        void RunAction(Action[] action)
        {
            if (action == null)
                return;

            int length = action.Length;
            if (stopOnError)
            {
                for (int i = 0; i < length; i++)
                    try
                    {
                        action[i]();
                    }
                    catch (Exception ex)
                    {
                        Stop();
                        throw (ex);
                    }
            }
            else
                for (int i = 0; i < length; i++)
                    action[i]();
        }

        #endregion

        // Composer (Non-action node) methods
        #region Composer
        bool IsComposer()
        {
            return type != Type.Action;
        }

        void ComposerUpdate()
        {
            // no children || removed
            if (children == null || children.Count == 0 || removeFlag)
                return;

            // 0. remove node, call time update
            if (type == Type.Root)
            {
                RemoveMarked();
                TimeTick();
            }

            // 1. masking children
            uint selectedChild = 0;
            uint activeChildOld = activeChild;
            activeChild = 0;

            switch (type)
            {
                case Type.Root:
                case Type.Parallel:
                    selectedChild = ParallelSelect(activeChildOld);
                    break;
                case Type.Selector:
                    selectedChild = RandomSelect(activeChildOld);
                    break;
                case Type.Priority:
                    selectedChild = PreoritySelect(activeChildOld);
                    break;
                case Type.PrioWait:
                    selectedChild = PreorityWaitSelect(activeChildOld);
                    break;
                case Type.Sequence:
                    selectedChild = SequenceSelect(activeChildOld, activeChildOld);
                    break;
            }

            // 2. Update children based on mask

            for (int i = 0; i < children.Count; i++)
            {
                uint b = 1u << i;
                // is selected or active ?
                if ((selectedChild & b) != 0)
                {
                    Bhv child = children[i];

                    // Start
                    if (!child.IsActive())
                    {
                        Start();
                        child.Start();
                    }

                    // Update
                    child.Update();

                    // Mark if still active
                    if (child.IsActive())
                        activeChild |= b;
                }
            }

            // 3. Check Stop
            if (type != Type.Root)
            {
                bool noActive = activeChild == 0;
                bool isSeq = type == Type.Sequence;

                // StopCondition
                if (duration.Enabled || stopCondition != null)
                {
                    if (IsOnStop())
                        Stop();
                    // Sequence: no active in sequence, check next
                    else if (noActive && isSeq)
                    {
                        activeChild = SequenceSelect(activeChildOld, activeChild);

                        // No next, wait
                        if (activeChild == 0)
                            activeChild = activeChildOld; //bug, will restart current again?
                    }
                }
                // No active
                else if (noActive)
                {
                    // Sequence
                    if (isSeq)
                    {
                        activeChild = SequenceSelect(activeChildOld, activeChild);

                        // No next
                        if (activeChild == 0)
                            Stop();
                    }
                    else
                        Stop();
                }
            }
        }

        uint ParallelSelect(uint activeMask)
        {
            // clear all
            activeMask = 0;

            for (int i = 0, length = Count; i < length; i++)
            {
                Bhv node = children[i];

                if (node.IsActive() || node.IsOnStart())
                    activeMask |= 1u << i;
            }

            return activeMask;
        }

        uint RandomSelect(uint activeMask)
        {
            // is current active ?
            if (activeMask != 0)
                return 0u;

            // index in children
            int index = -1;
            // index in actives
            int select = -1;
            // total number of actives
            int total = 0;
            // random percentage
            float r = UnityEngine.Random.value;

            for (int i = 0; i < children.Count; i++)
            {
                if (children[i].IsOnStart())
                {
                    total++;
                    int newSelect = (int)(total * r);
                    if (newSelect > select)
                    {
                        select = newSelect;
                        index = i;
                    }
                }
            }

            return index == -1 ? 0u : 1u << index;
        }

        uint SequenceSelect(uint activeChildOld, uint activeChild)
        {
            // Stay in current
            if (activeChild != 0)
                return activeChild;
            // First
            else if (activeChildOld == 0)
                return 1;
            // Check next
            else
            {
                // move to next, use old mask
                int next = activeChildOld.Top1Index() + 1;

                // has next and on condition ?
                return next < children.Count && children[next].IsOnStart() ? 1u << next : 0;
            }
        }

        uint PreoritySelect(uint activeMask)
        {
            int current = activeMask.Top1Index();

            // select the first that is on condition
            for (int i = 0; i < children.Count; i++)
            {
                if (children[i].IsOnStart())
                {
                    // new < old || old.stoped
                    bool change =
                        (current == -1) ||
                        (i < current) ||
                        (i > current && !children[current].IsActive());

                    if (change)
                    {
                        // stop old one
                        if (current != -1)
                            children[current].Stop();

                        current = i;
                    }

                    break;
                }
            }

            return current != -1 ? 1u << current : 0u;
        }

        uint PreorityWaitSelect(uint activeMask)
        {
            // is current active ?
            if (activeMask != 0)
                return activeMask;

            return PreoritySelect(activeMask);
        }

        void ComposerStop()
        {
            activeChild = 0;
        }
        #endregion

        // Timers
        #region Timers
        /// <summary>
        /// Set fixed duration
        /// </summary>
        /// <param name="target">Target.</param>
        public void Duration(float target)
        {
            duration.Enabled = true;
            duration.Target = target;
        }

        /// <summary>
        /// Set random duration
        /// </summary>
        /// <param name="min">Minimum.</param>
        /// <param name="max">Max.</param>
        public void Duration(float min, float max)
        {
            duration.Enabled = true;
            duration.SetRange(min, max);
        }

        /// <summary>
        /// Use cooldown timer
        /// </summary>
        /// <param name="time">Cooldown time</param>
        /// <param name="startInst">Is cooling down start on behavior start or on behavior stop </param>
        public void CD(float time, bool startInst = false)
        {
            cdTimer.Enabled = true;
            cdTimer.Target = time;
            cdTimer.startInit = startInst;
        }

        public struct BehaviorTimer
        {
            public float min;
            public float max;

            public TimeCounter timeCount;

            public float Time { get { return timeCount.time; } }
            public float Completion { get { return timeCount.Completion; } }

            public bool Enabled {
                get {
                    return timeCount.enable;
                }
                set {
                    timeCount.enable = value;
                }
            }

            public float Target {
                get { return timeCount.target;  }
                set { timeCount.target = value; }
            }

            public void Start()
            {
                if (IsRandom())
                    RandomTarget();

                timeCount.Start();
            }

            public void End()
            {
                timeCount.End();
            }

            public void Update()
            {
                timeCount.Update();
            }

            public bool ReachTarget()
            {
                return timeCount.ReachTarget();
            }

            /*
            public override bool HasTarget()
            {
                return random ? min < max && max > 0 : target > 0;
            }*/

            public bool IsRandom()
            {
                return min != max;
            }

            public bool HasRandomTarget()
            {
                return min < max && max > 0;
            }

            public void SetRange(float min, float max)
            {
                this.min = min;
                this.max = max;
            }

            public void RandomTarget()
            {
                timeCount.target = UnityEngine.Random.Range(min, max);
                timeCount.Start();
            }

            public void RandomTarget(float min, float max)
            {
                SetRange(min, max);
                RandomTarget();
            }
        }
        #endregion

        // Child management
        #region Child Management
        public void Add(params Bhv[] value)
        {
            AddChildrenImpl(i => value[i], value.Length);
        }

        public void Add(params Func<Bhv>[] value)
        {
            AddChildrenImpl(i => value[i](), value.Length);
        }

        void AddChildrenImpl(Func<int, Bhv> value, int total)
        {
            // ensure capacity
            if(children == null)
                children = new List<Bhv>(total);
            else
                children.Capacity = children.Count + total;

            int cLevel = nodeLevel + 1;
            for (int i = 0; i < total; i++)
            {
                Bhv c = value(i);

                // remove from old
                c._parent?.Remove(c);

                // set parent, index
                c._parent = this;
                c.nodeLevel = cLevel;
                c.nodeIndex = children.Count;

                // add to array
                children.Add(c);
            }
        }

        public Bhv Get(int childIndex)
        {
            return children[childIndex];
        }

        public void Remove(Bhv child)
        {
            if (children != null)
            {
                if (children[child.nodeIndex] == child)
                {
                    children.RemoveAt(child.nodeIndex);
                    UpdateChildrenIndex(child.nodeIndex);
                }
                else
                {
                    Debug.LogError("Children does not contain the node at position: " + child.nodeIndex);
                }
            }
        }

        /// <summary>
        /// Mark this node's removeFlag, remove on next update
        /// </summary>
        public void Remove()
        {
            removeFlag = true;
        }

        /// <summary>
        /// Remove and Stop all marked child
        /// </summary>
        protected void RemoveMarked()
        {
            if (children != null)
            {
                children.RemoveAll(c =>
                {
                    if (c.removeFlag)
                    {
                        c.Stop();
                        c._parent = null;
                    }
                    return c.removeFlag;
                });

                UpdateChildrenIndex();
            }
        }

        void UpdateChildrenIndex(int startIndex = 0)
        {
            for (int i = startIndex; i < children.Count; i++)
                children[i].nodeIndex = i;
        }
        #endregion

        // Factory Methods
        #region Factory
        public static Bhv Create(Type type)
        {
            return new Bhv
            {
                type = type
            };
        }

        public static Bhv Create(Type type, string name)
        {
            return new Bhv()
            {
                type = type,
                name = name
            };
        }

        public static Bhv Create(Type type, params Bhv[] nodes)
        {
            Bhv bhv = Create(type);
            bhv.Add(nodes);
            return bhv;
        }

        public static Bhv Create(Type type, params Func<Bhv>[] func)
        {
            Bhv bhv = Create(type);
            bhv.Add(func);
            return bhv;
        }

        // Action
        public static Bhv Act()
        {
            return Create(Type.Action);
        }

        // Root
        public static Bhv Root()
        {
            return Create(Type.Root);
        }

        public static Bhv Root(params Bhv[] nodes)
        {
            return Create(Type.Root, nodes);
        }

        public static Bhv Root(params Func<Bhv>[] func)
        {
            return Create(Type.Root, func);
        }

        // Parallel
        public static Bhv Parallel()
        {
            return Create(Type.Parallel);
        }

        public static Bhv Parallel(params Bhv[] nodes)
        {
            return Create(Type.Parallel, nodes);
        }

        public static Bhv Parallel(params Func<Bhv>[] func)
        {
            return Create(Type.Parallel, func);
        }

        // Selector
        public static Bhv Selector()
        {
            return Create(Type.Selector);
        }

        public static Bhv Selector(params Bhv[] nodes)
        {
            return Create(Type.Selector, nodes);
        }

        public static Bhv Selector(params Func<Bhv>[] func)
        {
            return Create(Type.Selector, func);
        }

        // Sequence
        public static Bhv Sequence()
        {
            return Create(Type.Sequence);
        }

        public static Bhv Sequence(params Bhv[] nodes)
        {
            return Create(Type.Sequence, nodes);
        }

        public static Bhv Sequence(params Func<Bhv>[] func)
        {
            return Create(Type.Sequence, func);
        }

        // Preority
        public static Bhv Priority()
        {
            return Create(Type.Priority);
        }

        public static Bhv Priority(params Bhv[] nodes)
        {
            return Create(Type.Priority, nodes);
        }

        public static Bhv Priority(params Func<Bhv>[] func)
        {
            return Create(Type.Priority, func);
        }

        // PreorityWait
        public static Bhv PrioWait()
        {
            return Create(Type.PrioWait);
        }

        public static Bhv PrioWait(params Bhv[] nodes)
        {
            return Create(Type.PrioWait, nodes);
        }

        public static Bhv PrioWait(params Func<Bhv>[] func)
        {
            return Create(Type.PrioWait, func);
        }
        #endregion

        public static bool IsComposer(Type type)
        {
            return type != Type.None && type != Type.Action;
        }

        #region Text
        public override string ToString()
        {
            string result = null;

            bool hasName = !string.IsNullOrEmpty(name);

            if (type == Type.None)
            {
                if (hasName)
                    result = name;
                else
                    result = type.ToString();
            }
            else if (type == Type.Action)
            {
                if (hasName)
                    result = name;
                else
                    result = type.ToString();
            }
            else
            {
                result = type.ToString();
                if (hasName)
                    result += "(" + name + ")";
            }

            return result;
        }

        public string GetHierarchyText(StringBuilder sb = null)
        {
            if (sb == null)
                sb = new StringBuilder();
            GetHierarchyText(this, sb);
            return sb.ToString();
        }

        public static void GetHierarchyText(Bhv bhv, StringBuilder sb)
        {
            Type type = bhv.type;

            string text = bhv.ToString();

            if (type == Type.None)
            {
                sb.Append(text);
            }
            else if (type == Type.Action)
            {
                sb.Append(text);
            }
            else
            {
                sb.Append(text).Append("{\n");

                var children = bhv.children;
                for (int i = 0, length = bhv.Count; i < length; i++)
                {
                    if (i > 0)
                        sb.Append(", ");

                    var b = children[i];
                    GetHierarchyText(b, sb);
                }

                sb.Append("}\n");
            }
        }
        #endregion
    }
}